#include "RuntimeStatistics.cuh"

RuntimeStatistics::RuntimeStatistics(int number_of_blocks, int number_of_threads)
{
	this->number_of_blocks = number_of_blocks;
	this->number_of_threads = number_of_threads;

	alloc_stats(number_of_blocks, number_of_threads);
	clear_stats(number_of_blocks, number_of_threads);

}

__device__ int RuntimeStatistics::get_index()
{
	//return threadIdx.x + blockIdx.x * blockDim.x;
	return threadIdx.x + (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x;
}

__device__ void RuntimeStatistics::signal_job_start()
{
	signal_start(total_solving_time_dev, number_of_jobs_run_dev);
}
__device__ void RuntimeStatistics::signal_job_stop()
{
	signal_stop(total_solving_time_dev);
}

__host__ __device__ long long int* RuntimeStatistics::get_total_solving_time()
{

#ifndef __CUDA_ARCH__
	int size = sizeof(long long int)*number_of_blocks*number_of_threads;
	long long int* tot_solving_time = (long long int*) malloc(size);
	cudaMemcpy(tot_solving_time, total_solving_time_dev, size, cudaMemcpyDeviceToHost);
	return tot_solving_time;

#else
	return total_solving_time_dev;
#endif
}
__host__ __device__ int* RuntimeStatistics::get_number_of_jobs_run()
{
#ifndef __CUDA_ARCH__
	int size = sizeof(int)*number_of_blocks*number_of_threads;
	int* numb_of_jobs_run = (int*) malloc(size);
	cudaMemcpy(numb_of_jobs_run, number_of_jobs_run_dev, size, cudaMemcpyDeviceToHost);
	return numb_of_jobs_run;

#else
	return number_of_jobs_run_dev;
#endif
}

__device__ void RuntimeStatistics::signal_preprocess_start()
{
	signal_start(total_time_on_gpu_preprocessing_dev, number_of_preprocesses_made_dev);
}
__device__ void RuntimeStatistics::signal_preprocess_stop()
{
	signal_stop(total_time_on_gpu_preprocessing_dev);
}

__device__ void RuntimeStatistics::signal_reset_structures_start()
{
	signal_start(total_time_resetting_structures_dev, number_of_resets_done);
}
__device__ void RuntimeStatistics::signal_reset_structures_stop()
{
	signal_stop(total_time_resetting_structures_dev);
}


__device__ void RuntimeStatistics::signal_conflict_analysis_start(int decision_level)
{
	int index = get_index();
	signal_start(total_time_analyzing_conflict_dev, number_conflict_analysis_made_dev);
	last_backtracked_start_level_dev[index] = decision_level;
}
__device__ void RuntimeStatistics::signal_conflict_analysis_stop(int decision_level)
{
	int index = get_index();
	signal_stop(total_time_analyzing_conflict_dev);


	if(decision_level < last_backtracked_start_level_dev[index])
	{
		long int value = last_backtracked_start_level_dev[index] - decision_level;
		total_backtracked_levels_dev[index] += value;
		backtracked_levels_instances_count_dev[index]++;
	}
}

__device__ void RuntimeStatistics::signal_decision_start()
{
	signal_start(total_time_deciding_dev, number_decisions_made_dev);
}
__device__ void RuntimeStatistics::signal_decision_stop()
{
	signal_stop(total_time_deciding_dev);
}
__device__ void RuntimeStatistics::signal_backtrack_start(int number_of_decisions)
{
	signal_start(total_time_backtracking_dev, number_backtracks_made_dev);
	int index = get_index();

	sum_of_decision_before_backtracking_dev[index] += number_of_decisions;

}
__device__ void RuntimeStatistics::signal_backtrack_stop(int number_of_decisions)
{
	signal_stop(total_time_backtracking_dev);
	int index = get_index();
	sum_of_decision_after_backtracking_dev[index] += number_of_decisions;
}

__device__ void RuntimeStatistics::signal_create_structures_start()
{
	signal_start(total_time_creating_structures_dev, number_of_create_structures_done);
}
__device__ void RuntimeStatistics::signal_create_structures_stop()
{
	signal_stop(total_time_creating_structures_dev);
}
__device__ void RuntimeStatistics::signal_next_job_start()
{
	signal_start(total_time_on_next_job_dev, number_of_next_job_done);
}
__device__ void RuntimeStatistics::signal_next_job_stop()
{
	signal_stop(total_time_on_next_job_dev);
}
__device__ void RuntimeStatistics::signal_add_jobs_to_assumptions_start()
{
	signal_start(total_time_on_add_jobs_to_assumptions_dev, number_of_add_jobs_to_assumptions_done);
}
__device__ void RuntimeStatistics::signal_add_jobs_to_assumptions_stop()
{
	signal_stop(total_time_on_add_jobs_to_assumptions_dev);
}
__device__ void RuntimeStatistics::signal_process_results_start()
{
	signal_start(total_time_processing_results_dev, number_of_process_results_done);
}
__device__ void RuntimeStatistics::signal_process_results_stop()
{
	signal_stop(total_time_processing_results_dev);
}

__device__ void RuntimeStatistics::signal_pre_proc_handle_assumptions_start()
{
	signal_start(total_time_on_gpu_preprocessing_handling_assumption_dev, number_of_preprocesses_handling_assumption_made_dev);
}
__device__ void RuntimeStatistics::signal_pre_proc_handle_assumptions_stop()
{
	signal_stop(total_time_on_gpu_preprocessing_handling_assumption_dev);
}
__device__ void RuntimeStatistics::signal_pre_proc_add_to_graph_start()
{
	signal_start(total_time_on_gpu_preprocessing_adding_to_graph_dev, number_of_preprocesses_adding_to_graph_made_dev);
}
__device__ void RuntimeStatistics::signal_pre_proc_add_to_graph_stop()
{
	signal_stop(total_time_on_gpu_preprocessing_adding_to_graph_dev);
}
__device__ void RuntimeStatistics::signal_pre_proc_handling_variables_start()
{
	signal_start(total_time_on_gpu_preprocessing_handling_variables_dev, number_of_preprocesses_handling_variables_made_dev);
}
__device__ void RuntimeStatistics::signal_pre_proc_handling_variables_stop()
{
	signal_stop(total_time_on_gpu_preprocessing_handling_variables_dev);
}


__device__ void RuntimeStatistics::signal_start(long long int * total_time,
		int * occurrences)
{
	signal_start(total_time, occurrences, clock64());
}

__device__ void RuntimeStatistics::signal_start(long long int * total_time,
			int * occurrences, long long int time)
{
	int index = get_index();

	if(occurrences[index] == 0)
		total_time[index] = -time;//-(((long int) clock64()) >> STATISTICS_TIME_OFFSET);
	else
		total_time[index] -= time;//((long int) clock64()) >> STATISTICS_TIME_OFFSET;

	occurrences[index]++;

}

__device__ void RuntimeStatistics::signal_stop(
		long long int * total_time)
{
	signal_stop(total_time, clock64());
}

__device__ void RuntimeStatistics::signal_stop(long long int * total_time,
		long long int time)
{
	int index = get_index();
	total_time[index] += time;//((long int) clock64()) >> STATISTICS_TIME_OFFSET;
}

template<class T>
__host__ T* RuntimeStatistics::copy_to_host(bool block_thread_vector, T* element_dev)
{
	int size = sizeof(T);

	if(block_thread_vector)
		size *= number_of_blocks*number_of_threads;

	T * element_host = (T *) malloc(size);

	check(cudaMemcpy(element_host, element_dev,
				size, cudaMemcpyDeviceToHost), "Copying statistics data from device");

	return element_host;

}

__host__ void RuntimeStatistics::print_function_time_statistics()
{
	printf("\n*****Statistics******\n");
	printf("Total job's time:\n");
	print_stat_average(total_solving_time_dev, number_of_jobs_run_dev);
	print_stat_total(total_solving_time_dev, number_of_jobs_run_dev);

	printf("Pre-processing time:\n");
	print_stat_average(total_time_on_gpu_preprocessing_dev, number_of_preprocesses_made_dev);
	print_stat_total(total_time_on_gpu_preprocessing_dev, number_of_preprocesses_made_dev);

	printf("Decision time:\n");
	print_stat_average(total_time_deciding_dev, number_decisions_made_dev);
	print_stat_total(total_time_deciding_dev, number_decisions_made_dev);

	printf("Conflict analyzing time:\n");
	print_stat_average(total_time_analyzing_conflict_dev, number_conflict_analysis_made_dev);
	print_stat_total(total_time_analyzing_conflict_dev, number_conflict_analysis_made_dev);

	printf("Backtracking time:\n");
	print_stat_average(total_time_backtracking_dev, number_backtracks_made_dev);
	print_stat_total(total_time_backtracking_dev, number_backtracks_made_dev);

	printf("Structures reset time:\n");
	print_stat_average(total_time_resetting_structures_dev, number_of_resets_done);
	print_stat_total(total_time_resetting_structures_dev, number_of_resets_done);

	printf("Creating structures time:\n");
	print_stat_average(total_time_creating_structures_dev, number_of_create_structures_done);
	print_stat_total(total_time_creating_structures_dev, number_of_create_structures_done);

	printf("Next job time:\n");
	print_stat_average(total_time_on_next_job_dev, number_of_next_job_done);
	print_stat_total(total_time_on_next_job_dev, number_of_next_job_done);

	printf("Add jobs to assumptions time:\n");
	print_stat_average(total_time_on_add_jobs_to_assumptions_dev, number_of_add_jobs_to_assumptions_done);
	print_stat_total(total_time_on_add_jobs_to_assumptions_dev, number_of_add_jobs_to_assumptions_done);

	printf("Processing results time:\n");
	print_stat_average(total_time_processing_results_dev, number_of_process_results_done);
	print_stat_total(total_time_processing_results_dev, number_of_process_results_done);

	printf("Average backtracked levels:\n");
	print_stat_average(total_backtracked_levels_dev, backtracked_levels_instances_count_dev, false);

	printf("Pre-processing - handling assumptions time:\n");
	print_stat_average(total_time_on_gpu_preprocessing_handling_assumption_dev, number_of_preprocesses_handling_assumption_made_dev);
	print_stat_total(total_time_on_gpu_preprocessing_handling_assumption_dev, number_of_preprocesses_handling_assumption_made_dev);

	printf("Pre-processing - adding assumptions to graph time:\n");
	print_stat_average(total_time_on_gpu_preprocessing_adding_to_graph_dev, number_of_preprocesses_adding_to_graph_made_dev);
	print_stat_total(total_time_on_gpu_preprocessing_adding_to_graph_dev, number_of_preprocesses_adding_to_graph_made_dev);

	printf("Pre-processing - adding handling variables time:\n");
	print_stat_average(total_time_on_gpu_preprocessing_handling_variables_dev, number_of_preprocesses_handling_variables_made_dev);
	print_stat_total(total_time_on_gpu_preprocessing_handling_variables_dev, number_of_preprocesses_handling_variables_made_dev);

}

__host__ void RuntimeStatistics::print_stat_average(long long int* block_thread_stats_dev, int* count_dev, bool use_offset)
{
	long long int* stats_host = copy_to_host<long long int>(true, block_thread_stats_dev);
	int* count_host = copy_to_host<int>(true, count_dev);

	printf("Average:\n");

	for(int block = 0; block < number_of_blocks; block++)
	{
		for(int thread = 0; thread < number_of_threads; thread++)
		{
			int index = block*number_of_threads+thread;

			printf("\tBlock %d, thread %d: ", block, thread);

			if(count_host[index] == 0)
				printf("not run\n");
			else
			{
				if(use_offset)
					printf("%15.3f\n", ((double)(stats_host[index] << STATISTICS_TIME_OFFSET)/count_host[index]));
				else
					printf("%15.3f\n", ((double)(stats_host[index])/count_host[index]));
			}
		}
	}


	free(stats_host);
	free(count_host);
}

__host__ void RuntimeStatistics::print_stat_total(long long int* block_thread_stats_dev, int* count_dev)
{
	long long int* stats_host = copy_to_host<long long int>(true, block_thread_stats_dev);
	int* count_host = copy_to_host<int>(true, count_dev);

	printf("Total:\n");

	for(int block = 0; block < number_of_blocks; block++)
	{
		for(int thread = 0; thread < number_of_threads; thread++)
		{
			int index = block*number_of_threads+thread;

			printf("\tBlock %d, thread %d: ", block, thread);

			if(count_host[index] == 0)
				printf("not run\n");
			else
				printf("%lld, run %d times\n", (stats_host[index] << STATISTICS_TIME_OFFSET) , count_host[index]);
		}
	}


	free(stats_host);
	free(count_host);
}

template<class T>
__host__ void RuntimeStatistics::print_stat_sum(T* block_thread_stats_dev)
{

}

__host__ __device__ void RuntimeStatistics::print_average()
{
	long long int* time = get_total_solving_time();
	int* count = get_number_of_jobs_run();

	for(int i = 0; i < number_of_blocks; i++)
	{
		for(int j = 0; j < number_of_threads; j++)
		{
			int index = j+i*number_of_threads;
			long long int average = time[index]/count[index];

			printf("Block %d, thread %d has average job solving time = %lld\n", i, j, average);

		}
	}

#ifndef __CUDA_ARCH__
	free(time);
	free(count);
#endif

}

__device__ void RuntimeStatistics::add_completed_jobs(int completed_jobs)
{
	atomicAdd(all_threads_total_completed_jobs_dev, completed_jobs);
}

__host__ __device__ int RuntimeStatistics::get_all_threads_total_completed_jobs()
{
#ifndef __CUDA_ARCH__
	int value;
	cudaMemcpy(&value, all_threads_total_completed_jobs_dev, sizeof(int), cudaMemcpyDeviceToHost);
	return value;
#else
	return *all_threads_total_completed_jobs_dev;
#endif
}

__device__ long long int RuntimeStatistics::get_total_time_solving()
{
	int index = get_index();
	return total_solving_time_dev[index];
}

__device__ long long int RuntimeStatistics::get_total_deciding_time()
{
	int index = get_index();
	return total_time_deciding_dev[index];
}
__device__ long long int RuntimeStatistics::get_total_backtracking_time()
{
	int index = get_index();
	return total_time_backtracking_dev[index];
}
__device__ long long int RuntimeStatistics::get_total_conflict_analyzing_time()
{
	int index = get_index();
	return total_time_analyzing_conflict_dev[index];
}

__device__ int RuntimeStatistics::get_sum_of_decision_before_backtracking()
{
	int index = get_index();
	return sum_of_decision_before_backtracking_dev[index];
}
__device__ int RuntimeStatistics::get_sum_of_decision_after_backtracking()
{
	int index = get_index();
	return sum_of_decision_after_backtracking_dev[index];
}
__device__ long long int RuntimeStatistics::get_total_backtracked_levels()
{
	int index = get_index();
	return total_backtracked_levels_dev[index];
}


template<class T>
__host__ void clear_stat(T* & element, bool block_thread_vector, int number_of_blocks, int number_of_threads)
{

	size_t size = sizeof(T);
	T* host_temp;

	if(block_thread_vector)
	{
		size *= number_of_blocks*number_of_threads;
		host_temp = (T*) calloc(number_of_blocks*number_of_threads, sizeof(T));
	}
	else
	{
		host_temp = (T*) calloc(1, sizeof(T));
	}
	check(cudaMemcpy(element, host_temp, size,
		cudaMemcpyHostToDevice), "Clearing statistics on device");

	free(host_temp);

}
__host__ void RuntimeStatistics::clear_stats(int number_of_blocks, int number_of_threads)
{
	clear_stat<long long int>(total_solving_time_dev,
			true, number_of_blocks, number_of_threads);

	clear_stat<int>(number_of_jobs_run_dev,
			true, number_of_blocks, number_of_threads);

	// Partial time
	clear_stat<long long int>(total_time_analyzing_conflict_dev,
			true, number_of_blocks, number_of_threads);

	clear_stat<int>(number_conflict_analysis_made_dev,
			true, number_of_blocks, number_of_threads);

	clear_stat<long long int>(total_time_deciding_dev,
			true, number_of_blocks, number_of_threads);

	clear_stat<int>(number_decisions_made_dev,
			true, number_of_blocks, number_of_threads);

	clear_stat<long long int>(total_time_backtracking_dev,
			true, number_of_blocks, number_of_threads);

	clear_stat<int>(number_backtracks_made_dev,
			true, number_of_blocks, number_of_threads);

	clear_stat<long long int>(total_time_on_gpu_preprocessing_dev,
			true, number_of_blocks, number_of_threads);

	clear_stat<int>(number_of_preprocesses_made_dev,
			true, number_of_blocks, number_of_threads);

	clear_stat<int>(number_of_resets_done,
			true, number_of_blocks, number_of_threads);


	clear_stat<int>(number_of_create_structures_done,
			true, number_of_blocks, number_of_threads);

	clear_stat<int>(number_of_next_job_done,
			true, number_of_blocks, number_of_threads);

	clear_stat<int>(number_of_add_jobs_to_assumptions_done,
			true, number_of_blocks, number_of_threads);

	clear_stat<int>(number_of_process_results_done,
			true, number_of_blocks, number_of_threads);



	// Decisions
	clear_stat<int>(sum_of_decision_before_backtracking_dev,
			true, number_of_blocks, number_of_threads);

	clear_stat<int>(sum_of_decision_after_backtracking_dev,
				true, number_of_blocks, number_of_threads);

	clear_stat<long long int>(total_backtracked_levels_dev,
			true, number_of_blocks, number_of_threads);

	clear_stat<int>(last_backtracked_start_level_dev,
			true, number_of_blocks, number_of_threads);

	clear_stat<int>(backtracked_levels_instances_count_dev,
					true, number_of_blocks, number_of_threads);

	// Statistics from checking correction
	clear_stat<int>(all_threads_total_completed_jobs_dev,
			false, number_of_blocks, number_of_threads);

	// Pre-processing
	clear_stat<long long int>(total_time_on_gpu_preprocessing_handling_assumption_dev,
			true, number_of_blocks, number_of_threads);
	clear_stat<int>(number_of_preprocesses_handling_assumption_made_dev,
			true, number_of_blocks, number_of_threads);
	clear_stat<long long int>(total_time_on_gpu_preprocessing_adding_to_graph_dev,
			true, number_of_blocks, number_of_threads);
	clear_stat<int>(number_of_preprocesses_adding_to_graph_made_dev,
			true, number_of_blocks, number_of_threads);
	clear_stat<long long int>(total_time_on_gpu_preprocessing_handling_variables_dev,
			true, number_of_blocks, number_of_threads);
	clear_stat<int>(number_of_preprocesses_handling_variables_made_dev,
			true, number_of_blocks, number_of_threads);

}

template<class T>
__host__ void alloc_stat(T* & element, bool block_thread_vector,
		int number_of_blocks, int number_of_threads)
{
	size_t size = sizeof(T);

	if(block_thread_vector)
		size *= number_of_blocks*number_of_threads;

	check(cudaMalloc(&element,
		size), "Allocating structure for statistics");
}

__host__ void RuntimeStatistics::alloc_stats(int number_of_blocks, int number_of_threads)
{
	alloc_stat<long long int>(total_solving_time_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<int>(number_of_jobs_run_dev,
			true, number_of_blocks, number_of_threads);

	// Partial time
	alloc_stat<long long int>(total_time_analyzing_conflict_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<int>(number_conflict_analysis_made_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<long long int>(total_time_deciding_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<int>(number_decisions_made_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<long long int>(total_time_backtracking_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<int>(number_backtracks_made_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<long long int>(total_time_on_gpu_preprocessing_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<int>(number_of_preprocesses_made_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<long long int>(total_time_resetting_structures_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<int>(number_of_resets_done,
			true, number_of_blocks, number_of_threads);


	alloc_stat<long long int>(total_time_creating_structures_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<int>(number_of_create_structures_done,
			true, number_of_blocks, number_of_threads);

	alloc_stat<long long int>(total_time_on_next_job_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<int>(number_of_next_job_done,
			true, number_of_blocks, number_of_threads);

	alloc_stat<long long int>(total_time_on_add_jobs_to_assumptions_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<int>(number_of_add_jobs_to_assumptions_done,
			true, number_of_blocks, number_of_threads);

	alloc_stat<long long int>(total_time_processing_results_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<int>(number_of_process_results_done,
			true, number_of_blocks, number_of_threads);


	// Decisions
	alloc_stat<int>(sum_of_decision_before_backtracking_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<int>(sum_of_decision_after_backtracking_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<long long int>(total_backtracked_levels_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<int>(last_backtracked_start_level_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<int>(backtracked_levels_instances_count_dev,
			true, number_of_blocks, number_of_threads);

	// Statistics from checking correction
	alloc_stat<int>(all_threads_total_completed_jobs_dev,
			false, number_of_blocks, number_of_threads);


	// Pre-processing
	alloc_stat<long long int>(total_time_on_gpu_preprocessing_handling_assumption_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<int>(number_of_preprocesses_handling_assumption_made_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<long long int>(total_time_on_gpu_preprocessing_adding_to_graph_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<int>(number_of_preprocesses_adding_to_graph_made_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<long long int>(total_time_on_gpu_preprocessing_handling_variables_dev,
			true, number_of_blocks, number_of_threads);

	alloc_stat<int>(number_of_preprocesses_handling_variables_made_dev,
			true, number_of_blocks, number_of_threads);

}
